If you are using WPF Diagrams to represent a business object graph then you will typically store the business objects directly, for example in a database. However if you need a way to save and load diagrams without regard to any underlying business objects, WPF Diagrams provides a quick way to do so using XML.
Serializing Diagrams as XML
To save or load a diagram using XML, use the DiagramXmlSerializer class. The two main methods on this class are:
- Serialize serializes a Diagram to XML. It returns an XDocument object which you can save immediately, embed into a larger XML document or otherwise process as required.
- Deserialize deserializes an XDocument object containing the source XML into a Diagram object.
new DiagramXmlSerializer().Serialize(diagram).Save(fileName);
ds.Diagram = new DiagramXmlSerializer().Deserialize(XDocument.Load(fileName));
Handling Custom Shapes and Line Types
The DiagramXmlSerializer only knows about the shapes and line types built into WPF Diagrams. If you have defined your own shapes or line types then it will serialize them using the Name property, but for deserialization you will need to provide resolver methods to map these names to your own shapes or line types. A resolver method must have a single string, and a return type of DiagramShape or DiagramLineType as appropriate.
var serializer = new DiagramXmlSerializer { ShapeNameResolver = UserShapes.TryResolveShapeName, LineTypeNameResolver = UserShapes.TryResolveLineTypeName, }; public static class UserShapes { public static DiagramShape TryResolveShapeName(string name) { if (name == "WShape") return UserShapes.WShape; return null; } }
Serializing Custom Element Types
To serialize custom node or connection types, subclass DiagramXmlSerializer and override the NodeElementCore and ConnectionElementCore methods. You can call the base class methods for nodes and connections that are not of your custom types.
private static readonly XName MyNodeElementName = "MyNode"; protected override XElement NodeElementCore(DiagramNode node) { if (node is MyNode) { return new XElement(MyNodeElementName); } return base.NodeElementCore(node); }
protected override DiagramNode CreateNode(XElement xml) { if (xml.Name == MyNodeElementName) { return new MyNode(); } return base.CreateNode(xml); }
Serializing Custom Data
The DiagramXmlSerializer assumes that the Data property for nodes and connections contains text. If your node or connection’s Data value is not text, then you can override the way it is serialized and deserialized by setting the OnSerializeCustomNodeData, OnSerializeCustomConnectionData, OnDeserializeCustomNodeData and OnDeserializeCustomConnectionData callbacks.
The Serialize callbacks receive an XElement and a node or connection object. Your callback should examine the node or connection object data and, if it determines that the data requires custom serialization, it should add the necessary attributes or child elements to the XElement, and return true. If the data does not require custom serialization, the callback should return false, indicating that it did not process the data and the default (text) processing should be applied.
The Deserialize callbacks receive an XElement. Your callback should return the desired value of the node or connection Data property, or null to indicate that it did not process the element and the default (text) processing should be applied.
private static bool SerializeData(XElement element, DiagramNode node) { List<string> strings = node.Data as List<string>; if (strings != null) { element.Add(new XElement("Values", strings.Select(s => new XElement("Value", new XAttribute("Text", s))))); return true; } return false; // default serialization } private static object DeserializeData(XElement element) { XElement values = element.Element("Values"); if (values != null) { return values.Elements("Value") .Select(e => (string)(e.Attribute("Text"))) .ToList(); } return null; // default deserialization } // Usage: DiagramXmlSerializer serializer = new DiagramXmlSerializer { OnSerializeCustomNodeData = SerializeData, OnDeserializeCustomNodeData = DeserializeData };